package com.rectcircle.wechat.util;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
//TODO 完成重构和下载返回byte数组
/**
 * 模拟 http 请求并获得相应文本。<br>
 * 本类使用builder方式创建对象 <br>
 * 必选参数：访问 url <br>
 * 可选参数：<br>
 * param 请求参数<br>
 * charset 字符编码，默认为utf-8<br>
 * header 请求头<br>
 * postString post方式传送文本如json串
 * <h2>使用注意</h2>
 * <ul>
 * <li>使用get方式请求除了url仅可以选择设置chaset，param；配置其他参数无效
 * <li>使用post方式情趣所有参数均可配置，但是对于param，postString，postData，filePath配置大于1个，以左边的为准
 * </ul>
 * <p>
 * <h2>使用示例</h2>
 * <h3>示例1</h3>
 * 
 * <pre>
 * {@code //使用get方式获得返回数据}
 * String url = "https://www.baidu.com/"; //你要通过get方式访问的url
 * HttpRequest httpRequest = new HttpRequest.Builder(url)
 * 				.build();
 * String response =  httpRequest.getHtmlByGet();
 * System.out.println(response);
 * </pre>
 * 
 * <h3>示例2</h3>
 * 
 * <pre>
 * {@code //使用post方式提交表单并获得返回数据}
 * 
 * String url = ""; //你要通过post方式访问的url
 * Map<String, String> params = new HashMap<>();
 * params.put("key","val"); //post表单的数据
 * 
 * Map<String, String> header = new HashMap<>();
 * header.put("key","val"); //设置请求头
 * 
 * HttpRequest httpRequest = new HttpRequest.Builder(url)
 * 				.charset("gb2312") //配置字符集
 * 				.param(params) //设置请求参数
 * 				.header(headers) //设置请求头
 * 				.build();
 * String response =  httpRequest.getHtmlByPost();
 * System.out.println(response);
 * </pre>
 * 
 * <h3>示例3</h3>
 * 
 * <pre>
 * {@code //使用post方式提交字符串（如json）并获得返回数据}
 * 
 * String url = ""; //你要通过post方式访问的url
 * String json = ""; //你要通过post发送的json数据
 * 
 * Map<String, String> header = new HashMap<>();
 * header.put("key","val"); //设置请求头
 * 
 * HttpRequest httpRequest = new HttpRequest.Builder(url)
 * 				.charset("utf-8") //配置字符集
 * 				.postString(json) //设置请求参数
 * 				.header(headers) //设置请求头
 * 				.build();
 * String response =  httpRequest.getHtmlByPost();
 * System.out.println(response);
 * </pre>
 * 
 * <h3>示例4</h3>
 * 
 * <pre>
 * {@code //使用post/form方式上传文件}
 * 
 * String url = ""; //你要通过post方式访问的url
 * String filePath = "test.jpg"; //你要上传文件的路径
 * 
 * 
 * HttpRequest httpRequest = new HttpRequest.Builder(url)
				.postFile(filePath)
				.build();
				
 * String response =  httpRequest.getHtmlByPost();
 * System.out.println(response);
 * </pre>
 * 
 * @author RectCircle
 * @version 0.2<br>
 *          2016年7月11日-下午5:15:18<br>
 *          Copyright (c) 2016 RectCircle-版权所有<br>
 *
 */
public class HttpRequest {

	// 请求的url地址，必要
	private String url;
	// 可选参数
	// 参数
	private Map<String, String> param;
	// 字符编码
	private String charset;
	// 请求头
	private Map<String, String> header;
	// 请求结果
	private String result;
	// post文本
	private byte[] postData;

	// 私有的构造函数
	private HttpRequest(Builder builder) {
		this.url = builder.url;
		this.param = builder.param;
		this.charset = builder.charset;
		this.header = builder.header;
		this.postData = builder.postData;
	}

	/**
	 * 通过get方式请求
	 * 
	 * @return 请求得到的html页面
	 */
	public String getHtmlByGet() {
		HttpURLConnection conn = null;
		String getUrl = url;

		if (param != null && param.size() != 0) {
			// 处理请求的参数
			getUrl = url + "?" + handleParam();
		}
		try {
			URL url = new URL(getUrl);
			conn = (HttpURLConnection) url.openConnection();
			conn = setHeader(conn);
			// 禁止重定向
			conn.setInstanceFollowRedirects(false);
			result = readHtmlFromIO(conn);

		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (conn != null)
				conn.disconnect();
		}

		return result;

	}

	/**
	 * @param conn
	 * @return 收到的html字符串
	 * @throws UnsupportedEncodingException
	 * @throws IOException
	 */
	private String readHtmlFromIO(HttpURLConnection conn) throws UnsupportedEncodingException, IOException {
		BufferedReader reader = null;

		reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), charset));
		String tempLine = null;
		StringBuffer resultBuffer = new StringBuffer();
		try {
			while ((tempLine = reader.readLine()) != null) {
				resultBuffer.append(tempLine);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return resultBuffer.toString();
	}

    private String saveFile(HttpURLConnection conn, String dir) {
		String contentDisposition = conn.getHeaderField("Content-disposition");
		//Content-Type: image/jpg
		String contentType = conn.getHeaderField("Content-Type");
		
		if(contentDisposition==null && contentType.indexOf("text")!=-1){
			try {
				return readHtmlFromIO(conn);
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			return null;
		}
		
		String fileName;
		if(contentDisposition!=null){			
			fileName = StringTools.getFileNameByContentDisposition(contentDisposition);
		} else {
			fileName = UUID.randomUUID().toString()+"."+contentType.split("/")[1];
		}
		String filePath = StringTools.createFilePath(dir,fileName);
		
		FileOutputStream fos=null;
		try {
			File fileDir = new File(dir);
			if(!fileDir.exists()){
				fileDir.mkdir();
			}
			
			File file = new File(filePath);
			while(file.exists()){
				filePath = StringTools.rename(filePath);
				file = new File(filePath);
			}
			fos = new FileOutputStream(file);
			byte[] dat = IOTools.readInputStream(conn.getInputStream());
			fos.write(dat);
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if(fos!=null){
					fos.close();
					fos = null;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return filePath;
	}

	/**
	 * 处理get参数
	 */
	private String handleParam() {

		Iterator<String> it = param.keySet().iterator();

		StringBuffer sb = new StringBuffer();
		while (it.hasNext()) {
			String key = it.next();
			String value = param.get(key);
			try {
				sb.append(key + "=" + URLEncoder.encode(value, charset) + "&");
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
		}
		String paramString = sb.substring(0, sb.length() - 1);
		return paramString;
	}

	/**
	 * 设置HttpURLConnection请求头
	 * 
	 * @param conn
	 * @return 设置好请求头的HttpURLConnection连接
	 */
	private HttpURLConnection setHeader(HttpURLConnection conn) {
		if (header != null) {
			Iterator<String> it = header.keySet().iterator();
			while (it.hasNext()) {
				String key = (String) it.next();
				String value = header.get(key);

				conn.setRequestProperty(key, value);
			}
		}
		return conn;
	}

	/**
	 * 通过post方式请求
	 * 
	 * @return 请求得到的html页面
	 */
	public String getHtmlByPost() {
		HttpURLConnection conn = null;
		DataOutputStream out = null;

		try {
			URL url = new URL(this.url);
			conn = (HttpURLConnection) url.openConnection();
			conn.setInstanceFollowRedirects(false);
			conn.setDoOutput(true);
			conn.setDoInput(true);
			conn.setRequestMethod("POST");
			setHeader(conn);

			if (param != null && param.size() != 0) {
				postData = handleParam().getBytes(charset);
			}

			out = new DataOutputStream(conn.getOutputStream());
			out.write(postData);
			out.flush();

			result = readHtmlFromIO(conn);
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (conn != null)
					conn.disconnect();
				if (out != null)
					out.close();
			} catch (IOException e) {
				e.printStackTrace();
			}

		}

		return result;
	}

	/**
	 * 
	 * 通过get方式下载文件
	 * 
	 * @param dir 文件目录地址
	 */
	public String downloadByGet(String dir) {
		HttpURLConnection conn = null;
		String getUrl = url;

		if (param != null && param.size() != 0) {
			// 处理请求的参数
			getUrl = url + "?" + handleParam();
		}
		try {
			URL url = new URL(getUrl);
			conn = (HttpURLConnection) url.openConnection();
			conn = setHeader(conn);
			// 禁止重定向
			conn.setInstanceFollowRedirects(false);
			return saveFile(conn, dir);

		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (conn != null)
				conn.disconnect();
		}
		return null;

	}
	
	/**
	 * 
	 * 通过post方式下载文件
	 * 
	 * @param dir 文件目录地址
	 */
	public String downloadByPost(String dir) {
		HttpURLConnection conn = null;
		DataOutputStream out = null;

		try {
			URL url = new URL(this.url);
			conn = (HttpURLConnection) url.openConnection();
			conn.setInstanceFollowRedirects(false);
			conn.setDoOutput(true);
			conn.setDoInput(true);
			conn.setRequestMethod("POST");
			setHeader(conn);

			if (param != null && param.size() != 0) {
				postData = handleParam().getBytes(charset);
			}

			out = new DataOutputStream(conn.getOutputStream());
			out.write(postData);
			out.flush();

			result = saveFile(conn, dir);
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (conn != null)
					conn.disconnect();
				if (out != null)
					out.close();
			} catch (IOException e) {
				e.printStackTrace();
			}

		}
		return result;
	}

	/**
	 * 定义一个Builder静态类；
	 * 
	 * @author rectcircle
	 *
	 */
	public static class Builder {
		// 必须初始化的参数
		private String url;
		// 可选的参数，在此进行缺省赋值
		private Map<String, String> param = null;
		// 字符编码
		private String charset = "utf-8";
		// 请求头
		private Map<String, String> header = null;
		// post的数据
		private byte[] postData = null;
		// post字符串，可以是文本json、xml等
		private String postString = null;
		// post文件（上传文件）
		private String postFilePath = null;
		
		//专为微信上传永久视频素材特设的字段一般情况下用不到
		private String description = null;

		public Builder description(String description){
			this.description = description;
			return this;
		}
		
		// 构造函数参数为必传参数
		public Builder(String url) {
			this.url = url;
		}

		// 利用与属性名同名的方法设置可选参数并返回对象本身更
		public Builder charset(String charset) {
			this.charset = charset;
			return this;
		}

		public Builder param(Map<String, String> param) {
			this.param = param;
			return this;
		}

		public Builder header(Map<String, String> header) {
			this.header = header;
			return this;
		}

		public Builder postString(String postString) {
			this.postString = postString;
			return this;
		}

		public Builder postFile(String filePath) {
			this.postFilePath = filePath;
			return this;
		}

		public Builder postData(byte[] postData) {
			this.postData = postData;
			return this;
		}

		private void handleData() {
			if (postString != null) {
				postData = stringToBytes(postString, charset);
				return;
			}

			if (postData != null) {
				String boundary = "----" + System.currentTimeMillis(); // 分隔符（数据边界）
				if (header == null) {
					header = new HashMap<>();
					header.put("Content-Type", "multipart/form-data;boundary=" + boundary);
				}

				StringBuffer sb = new StringBuffer();
				sb.append("--"); // 必须多两道线
				sb.append(boundary);
				sb.append("\r\n");
				sb.append("Content-Disposition: form-data; name=\"media\"; filename=\"" + System.currentTimeMillis()
						+ "\"\r\n");
				sb.append("Content-Type: " + header.get("Content-Type") + "\r\n\r\n");
				byte[] before = stringToBytes(sb.toString(), charset);
				byte[] after = stringToBytes("\r\n--" + boundary + "--\r\n", charset);

				postData = byteMerge(byteMerge(before, postData), after);
				return;
			}

			if (postFilePath != null) {
				String boundary = "----" + System.currentTimeMillis(); // 分隔符（数据边界）
				if (header == null) {
					header = new HashMap<>();
					header.put("Content-Type", "multipart/form-data;boundary=" + boundary);
					// 定义请求头连接类型： form方式上传数据数据边界boundary...
				}
				// 以下配置form-data中媒体文件标识，有filename、filelength、content-type等信息
				StringBuffer sb = new StringBuffer();
				sb.append("--"); // 必须多两道线
				sb.append(boundary);
				sb.append("\r\n");
				sb.append("Content-Disposition: form-data; name=\"media\"; filename=\""
						+ StringTools.getFileNameByPath(postFilePath) + "\"\r\n");
				sb.append("Content-Type: " + header.get("Content-Type") + "\r\n\r\n");

				byte[] before = stringToBytes(sb.toString(), charset);

				byte[] fileData = fileToBytes(postFilePath);

				byte[] after = null;
				if(description!=null){
					sb = new StringBuffer();
					sb.append("\r\n--"); // 必须多两道线
					sb.append(boundary);
					sb.append("\r\n");
					sb.append("Content-Disposition: form-data; name=\"description\";\r\n\r\n");
					sb.append(description);
					sb.append("\r\n--" + boundary + "--\r\n");
					after = stringToBytes(sb.toString(), charset);
				} else {					
					after = stringToBytes("\r\n--" + boundary + "--\r\n", charset);
				}

				postData = byteMerge(byteMerge(before, fileData), after);
				return;
			}

		}

		private byte[] byteMerge(byte[] byte1, byte[] byte2) {
			byte[] byte3 = new byte[byte1.length + byte2.length];
			System.arraycopy(byte1, 0, byte3, 0, byte1.length);
			System.arraycopy(byte2, 0, byte3, byte1.length, byte2.length);
			return byte3;
		}

		// 将字符串转换为byte[]
		private byte[] stringToBytes(String str, String charset) {
			try {
				return str.getBytes(charset);
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
			return null;
		}

		private byte[] fileToBytes(String filePath) {
			BufferedInputStream in = null;
			ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
			try {
				in = new BufferedInputStream(new FileInputStream(filePath));
				byte[] temp = new byte[1024];
				int size = 0;
				while ((size = in.read(temp)) != -1) {
					out.write(temp, 0, size);
				}
				in.close();
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			return out.toByteArray();
		}

		public HttpRequest build() {
			handleData();
			return new HttpRequest(this);
		}

	}

}
